/*
 *
 *    Copyright (c) 2025 Project CHIP Authors
 *    All rights reserved.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
#pragma once

#include <app/server-cluster/DefaultServerCluster.h>
#include <clusters/WebRTCTransportProvider/ClusterId.h>
#include <clusters/WebRTCTransportProvider/Commands.h>
#include <clusters/WebRTCTransportProvider/Enums.h>
#include <clusters/WebRTCTransportProvider/Metadata.h>
#include <string>
#include <vector>

namespace chip {
namespace app {
namespace Clusters {
namespace WebRTCTransportProvider {

using ICEServerDecodableStruct = chip::app::Clusters::Globals::Structs::ICEServerStruct::DecodableType;
using WebRTCSessionStruct      = chip::app::Clusters::Globals::Structs::WebRTCSessionStruct::Type;
using ICECandidateStruct       = chip::app::Clusters::Globals::Structs::ICECandidateStruct::Type;
using StreamUsageEnum          = chip::app::Clusters::Globals::StreamUsageEnum;
using WebRTCEndReasonEnum      = chip::app::Clusters::Globals::WebRTCEndReasonEnum;

/**
 * @brief
 *   A delegate interface for the WebRTC Transport Provider cluster, allowing
 *   OEM or application-specific logic to be implemented outside of the cluster
 *   server itself. The server calls these methods to do the "real work."
 */
class Delegate
{
public:
    virtual ~Delegate() = default;

    struct OfferRequestArgs
    {
        uint16_t sessionId;
        StreamUsageEnum streamUsage;
        Optional<DataModel::Nullable<uint16_t>> videoStreamId;
        Optional<DataModel::Nullable<uint16_t>> audioStreamId;
        Optional<Structs::SFrameStruct::Type> sFrameConfig;
        Optional<std::vector<ICEServerDecodableStruct>> iceServers;
        Optional<std::string> iceTransportPolicy;
        NodeId peerNodeId;
        FabricIndex fabricIndex;
        EndpointId originatingEndpointId;
    };

    struct ProvideOfferRequestArgs : OfferRequestArgs
    {
        std::string sdp;
    };

    /**
     * @brief
     *     This method is called when the server receives the SolicitOffer command.
     *
     *     It is used by the requestor to ask the provider to initiate a new Offer/Answer
     *     negotiation. The specification states that the provider may be in a standby mode
     *     (low power), thus a "deferred offer" scenario might occur if the device needs time
     *     to fully power resources needed for streaming.
     *
     * @note
     *   - The incoming arguments in args include a freshly generated session ID (i.e. args.sessionId)
     *     that uniquely identifies this session. The delegate implementation MUST use this identifier
     *     for all subsequent processing and tracking of the session.
     *   - The `args.iceServers` data (including `urls`) is only valid during this method call.
     *   - The delegate **must** deep-copy any data (e.g., URLs) it needs to retain after this call
     *     returns. Storing references to the input data will cause undefined behavior.
     *
     * @param[in]  args
     *     Structure containing all input arguments for the command. In particular, args.sessionId
     *     is pre-populated with a unique session identifier generated by the server.
     *
     * @param[out] outSession
     *     On success (`CHIP_NO_ERROR`), must be populated with the new session's details.
     *
     * @param[out] outDeferredOffer
     *     On success (`CHIP_NO_ERROR`), set to `true` if the device is in standby
     *     or needs to defer generating an Offer; otherwise, `false`.
     *     If an error is returned, this value is not relevant.
     *
     * @return CHIP_ERROR
     *     - CHIP_NO_ERROR if a session was successfully created or prepared for negotiation.
     *     - CHIP_ERROR_NO_MEMORY if the device cannot allocate a new session.
     *     - Appropriate error otherwise.
     *
     * The combination of `outDeferredOffer == true` with an error return code is invalid
     * (the device cannot be "deferring" if it is failing the command).
     */
    virtual CHIP_ERROR HandleSolicitOffer(const OfferRequestArgs & args, WebRTCSessionStruct & outSession,
                                          bool & outDeferredOffer) = 0;

    /**
     * @brief
     *   Handles the ProvideOffer command received by the server.
     *
     * @note
     *   - The incoming arguments in args include a freshly generated session ID (i.e. args.sessionId)
     *     that uniquely identifies this session. The delegate implementation MUST use this identifier
     *     for all subsequent processing and tracking of the session.
     *   - The `args.iceServers` data (including `urls`) is only valid during this method call.
     *   - The delegate **must** deep-copy any data (e.g., URLs) it needs to retain after this call
     *     returns. Storing references to the input data will cause undefined behavior.
     *
     * @param[in]  args
     *   Contains all input arguments for the command, including the SDP Offer, session usage, etc.
     *   If the video stream ID is missing, the delegate MUST automatically select a matching video stream.
     *   If the audio stream ID is missing, the delegate MUST automatically select a matching audio stream.
     *   In either case, upon successful selection, the delegate is responsible for internally incrementing
     *   the reference count on any used video and audio streams.
     *
     * @param[out] outSession
     *   Must be populated with the final session details (session ID, stream IDs, etc.) when this
     *   method returns `CHIP_NO_ERROR`. If an error is returned, `outSession` is left unmodified or
     *   set to an invalid state.
     *
     * @return
     *   - CHIP_NO_ERROR if the request succeeds and `outSession` is populated.
     *   - CHIP_ERROR_NO_MEMORY if the device cannot allocate a new session.
     *   - Appropriate error otherwise.
     */
    virtual CHIP_ERROR HandleProvideOffer(const ProvideOfferRequestArgs & args, WebRTCSessionStruct & outSession) = 0;

    /**
     * @brief
     *   Called when the server receives the ProvideAnswer command.
     *
     *   In the standard WebRTC Offer/Answer flow, once the provider sends an Offer (or
     *   the requestor has re-offered), the other side will respond with an SDP Answer.
     *   This method is invoked to store and process that Answer.
     *
     * @param[in] sessionId  The identifier of the current session.
     * @param[in] sdpAnswer  The SDP Answer string.
     *
     * @return CHIP_ERROR
     *   - CHIP_NO_ERROR if the Answer is accepted
     *   - Appropriate error if invalid or unexpected
     */
    virtual CHIP_ERROR HandleProvideAnswer(uint16_t sessionId, const std::string & sdpAnswer) = 0;

    /**
     * @brief
     *   Called when the server receives the ProvideICECandidates command.
     *
     *   After the initial Offer/Answer exchange, additional ICE Candidates may be
     *   discovered. This command allows the requestor to provide those candidates
     *   (i.e., Trickle ICE).
     *
     * @param[in] sessionId  The current session ID.
     * @param[in] candidates A list of ICE candidate structs.
     * Note: The callee cannot reference the `candidates` vector after this call
     * returns, and must copy the contents over for later use, if required.
     *
     * @return CHIP_ERROR
     *   - CHIP_NO_ERROR on success
     *   - An error if the session is invalid or the candidates cannot be processed
     */
    virtual CHIP_ERROR HandleProvideICECandidates(uint16_t sessionId, const std::vector<ICECandidateStruct> & candidates) = 0;

    /**
     * @brief
     *   Called when the server or the requestor ends a session via the EndSession command.
     *
     *   Internally decrement the ReferenceCount on any used video streams.
     *   Internally decrement the ReferenceCount on any used audio streams.
     *
     * @param[in] sessionId      The ID of the session to end.
     * @param[in] reasonCode     Reason for ending the session (e.g. normal closure, resource limit).
     * @param[in] videoStreamID  The nullable ID of the video stream associated with the session.
     * @param[in] audioStreamID  The nullable ID of the audio stream associated with the session.
     *
     * @return CHIP_ERROR
     *   - CHIP_NO_ERROR on success
     *   - Error if no matching session is found or some cleanup error occurs
     */
    virtual CHIP_ERROR HandleEndSession(uint16_t sessionId, WebRTCEndReasonEnum reasonCode,
                                        DataModel::Nullable<uint16_t> videoStreamID,
                                        DataModel::Nullable<uint16_t> audioStreamID) = 0;

    /**
     * @brief Validates the requested stream usage against the camera's resource management
     *        and stream priority policies.
     *
     * The implementation SHALL ensure:
     *  - The requested stream usage (streamUsage) is allowed given the current allocation of
     *    camera resources (e.g. CPU, memory, network bandwidth) and the prioritized stream list.
     *  - If the provided IDs are null, it matches against an allocated stream with the same stream usage
     *    and updates the IDs to those for the matching stream
     *
     * @param[in] streamUsage    The desired usage type for the stream (e.g. live view, recording, etc.).
     * @param[in,out] videoStreamId  Optional identifier for the requested video stream.
     * @param[in,out] audioStreamId  Optional identifier for the requested audio stream.
     *
     * @return CHIP_ERROR CHIP_NO_ERROR if the stream usage is valid; an appropriate error code otherwise.
     */
    virtual CHIP_ERROR ValidateStreamUsage(StreamUsageEnum streamUsage, Optional<DataModel::Nullable<uint16_t>> & videoStreamId,
                                           Optional<DataModel::Nullable<uint16_t>> & audioStreamId) = 0;

    /**
     * @brief
     *   Validates that the given VideoStreamID matches a value in AllocatedVideoStreams.
     *
     *   This method is called during SolicitOffer command processing when a VideoStreamID
     *   is present and not null. The implementation must check if the provided ID exists
     *   in the AllocatedVideoStreams list from the CameraAvStreamManagement cluster.
     *
     * @param[in] videoStreamId  The video stream ID to validate.
     *
     * @return CHIP_ERROR
     *   - CHIP_NO_ERROR if the VideoStreamID is valid and matches AllocatedVideoStreams.
     *   - CHIP_ERROR_NOT_FOUND or other appropriate error if validation fails or a match can't be found
     */
    virtual CHIP_ERROR ValidateVideoStreamID(uint16_t videoStreamId) = 0;

    /**
     * @brief
     *   Validates that the given AudioStreamID matches a value in AllocatedAudioStreams.
     *
     *   This method is called during SolicitOffer command processing when an AudioStreamID
     *   is present and not null. The implementation must check if the provided ID exists
     *   in the AllocatedAudioStreams list from the CameraAvStreamManagement cluster.
     *
     * @param[in] audioStreamId  The audio stream ID to validate.
     *
     * @return CHIP_ERROR
     *   - CHIP_NO_ERROR if the AudioStreamID is valid and matches AllocatedAudioStreams.
     *   - CHIP_ERROR_NOT_FOUND or other appropriate error if validation fails.
     */
    virtual CHIP_ERROR ValidateAudioStreamID(uint16_t audioStreamId) = 0;

    /**
     * @brief
     *   Checks if the given StreamUsage is in the StreamUsagePriorities list.
     *
     *   This method is called during SolicitOffer and ProvideOffer command processing
     *   to ensure that the requested stream usage is supported by the device according
     *   to its configured priorities.
     *
     * @param[in] streamUsage  The stream usage to check.
     *
     * @return CHIP_ERROR
     *   - CHIP_NO_ERROR - StreamUsage is found in StreamUsagePriorities.
     *   - CHIP_ERROR_NOT_FOUND - StreamUsage is not in the StreamUsagePriorities list.
     *   - Other error codes - For failures reading the attribute itself
     */
    virtual CHIP_ERROR IsStreamUsageSupported(Globals::StreamUsageEnum streamUsage) = 0;

    /**
     * @brief Check whether hard privacy mode is active.
     *
     * Reads the HardPrivacyModeOn attribute from the CameraAvStreamManagement cluster.
     *
     * @param[out] isActive Set to true if privacy mode is active, false if inactive.
     * @return CHIP_NO_ERROR on success, error code if privacy mode state cannot be determined.
     */
    virtual CHIP_ERROR IsHardPrivacyModeActive(bool & isActive) = 0;

    /**
     * @brief Check whether soft recording privacy mode is active.
     *
     * Reads the SoftRecordingPrivacyModeEnabled attribute from the CameraAvStreamManagement cluster.
     *
     * @param[out] isActive Set to true if privacy mode is active, false if inactive.
     * @return CHIP_NO_ERROR on success, error code if privacy mode state cannot be determined.
     */
    virtual CHIP_ERROR IsSoftRecordingPrivacyModeActive(bool & isActive) = 0;

    /**
     * @brief Check whether soft livestream privacy mode is active.
     *
     * Reads the SoftLivestreamPrivacyModeEnabled attribute from the CameraAvStreamManagement cluster.
     *
     * @param[out] isActive Set to true if privacy mode is active, false if inactive.
     * @return CHIP_NO_ERROR on success, error code if privacy mode state cannot be determined.
     */
    virtual CHIP_ERROR IsSoftLivestreamPrivacyModeActive(bool & isActive) = 0;

    /**
     * @brief Check if there are any allocated video streams.
     *
     * This method is called during ProvideOffer command processing when VideoStreamID
     * is present and null to determine if automatic stream selection is possible.
     *
     * @return true if there are allocated video streams available.
     * @return false if no video streams are currently allocated.
     */
    virtual bool HasAllocatedVideoStreams() = 0;

    /**
     * @brief Check if there are any allocated audio streams.
     *
     * This method is called during ProvideOffer command processing when AudioStreamID
     * is present and null to determine if automatic stream selection is possible.
     *
     * @return true if there are allocated audio streams available.
     * @return false if no audio streams are currently allocated.
     */
    virtual bool HasAllocatedAudioStreams() = 0;

    /**
     * @brief Validates the SFrame configuration including cipher suite and base key length.
     *
     * The implementation SHALL ensure:
     *  - The cipher suite is a supported value (e.g., AES-128-GCM or AES-256-GCM).
     *  - The base key length matches the expected length for the specified cipher suite.
     *
     * @param[in] cipherSuite    The cipher suite identifier from the SFrame configuration.
     * @param[in] baseKeyLength  The length of the base key in bytes.
     *
     * @return CHIP_ERROR CHIP_NO_ERROR if the SFrame configuration is valid; an appropriate error code otherwise.
     */
    virtual CHIP_ERROR ValidateSFrameConfig(uint16_t cipherSuite, size_t baseKeyLength) = 0;

    /**
     * @brief Checks if the Time Synchronization cluster's UTCTime attribute is null.
     *
     * Per the Matter spec, when ICEServer URLs with scheme 'turns' or 'stuns' are provided,
     * the Time Synchronization cluster's UTCTime attribute must not be null. This method
     * allows the WebRTC Transport Provider cluster to perform this cross-cluster validation.
     *
     * The implementation SHALL:
     *  - Read the UTCTime attribute from the Time Synchronization cluster (0x0038)
     *  - Return whether the attribute is null or has a valid value
     *
     * @param[out] isNull  Set to true if UTCTime is null, false if it has a valid value.
     *
     * @return CHIP_ERROR
     *   - CHIP_NO_ERROR on success (isNull indicates the state)
     *   - CHIP_ERROR_NOT_FOUND if Time Synchronization cluster is not present
     *   - Other appropriate error codes for read failures
     */
    virtual CHIP_ERROR IsUTCTimeNull(bool & isNull) = 0;
};

class WebRTCTransportProviderCluster : public DefaultServerCluster
{
public:
    /**
     * @brief
     *   Constructs the WebRTCTransportProviderCluster with the specified delegate and endpoint.
     *
     * @param[in] endpointId The Endpoint where the WebRTC Transport Provider cluster is published.
     * @param[in] delegate   A reference to an implementation of the Delegate interface. Must remain
     *                       valid for the lifetime of this object.
     */
    WebRTCTransportProviderCluster(EndpointId endpointId, Delegate & delegate);

    DataModel::ActionReturnStatus ReadAttribute(const DataModel::ReadAttributeRequest & request,
                                                AttributeValueEncoder & encoder) override;

    std::optional<DataModel::ActionReturnStatus> InvokeCommand(const DataModel::InvokeRequest & request,
                                                               TLV::TLVReader & input_arguments, CommandHandler * handler) override;

    CHIP_ERROR AcceptedCommands(const ConcreteClusterPath & path,
                                ReadOnlyBufferBuilder<DataModel::AcceptedCommandEntry> & builder) override;

    CHIP_ERROR Attributes(const ConcreteClusterPath & path, ReadOnlyBufferBuilder<DataModel::AttributeEntry> & builder) override;

    /**
     * @brief Get a reference to the current WebRTC sessions.
     *
     * This method provides read-only access to the current list of active WebRTC sessions
     * managed by this server instance.
     *
     * @return const std::vector<WebRTCSessionStruct>& Reference to the current sessions list.
     */
    const std::vector<WebRTCSessionStruct> & GetCurrentSessions() const { return mCurrentSessions; }

    /**
     * @brief Removes a WebRTC session given a session ID.
     *
     */
    void RemoveSession(uint16_t sessionId);

private:
    enum class UpsertResultEnum : uint8_t
    {
        kInserted = 0x00,
        kUpdated  = 0x01,
    };

    Delegate & mDelegate;
    std::vector<WebRTCSessionStruct> mCurrentSessions;

    // Helper functions
    WebRTCSessionStruct * FindSession(uint16_t sessionId);
    WebRTCSessionStruct * CheckForMatchingSession(const CommandHandler & commandHandler, uint16_t sessionId);
    UpsertResultEnum UpsertSession(const WebRTCSessionStruct & session);
    CHIP_ERROR GenerateSessionId(uint16_t & outSessionId);
    Protocols::InteractionModel::Status CheckPrivacyModes(const char * commandName, StreamUsageEnum streamUsage);
    Protocols::InteractionModel::Status
    CheckTurnsOrStunsRequiresUTCTime(const char * commandName,
                                     const Optional<DataModel::DecodableList<ICEServerDecodableStruct>> & iceServers);

    // Command Handlers
    std::optional<DataModel::ActionReturnStatus> HandleSolicitOffer(CommandHandler & commandHandler,
                                                                    const Commands::SolicitOffer::DecodableType & req);
    std::optional<DataModel::ActionReturnStatus> HandleProvideOffer(CommandHandler & commandHandler,
                                                                    const Commands::ProvideOffer::DecodableType & req);
    std::optional<DataModel::ActionReturnStatus> HandleProvideAnswer(CommandHandler & commandHandler,
                                                                     const Commands::ProvideAnswer::DecodableType & req);
    std::optional<DataModel::ActionReturnStatus>
    HandleProvideICECandidates(CommandHandler & commandHandler, const Commands::ProvideICECandidates::DecodableType & req);
    std::optional<DataModel::ActionReturnStatus> HandleEndSession(CommandHandler & commandHandler,
                                                                  const Commands::EndSession::DecodableType & req);
};

} // namespace WebRTCTransportProvider
} // namespace Clusters
} // namespace app
} // namespace chip
